package webhook

import (
	"encoding/json"
	"fmt"
	"go.uber.org/zap"
	admissionv1 "k8s.io/api/admission/v1"
	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"regexp"
	"strings"
)

// mutationRequired 判断是否需要添加sidecar
func mutationRequired(metadata *metav1.ObjectMeta) bool {
	zap.L().Debug(funcParams, zap.Any("metadata", metadata))
	required := admissionRequired(annotationMutateKey, metadata)
	annotations := metadata.GetAnnotations()
	if annotations == nil {
		annotations = map[string]string{}
	}
	status := annotations[annotationStatusKey]

	if strings.ToLower(status) == "mutated" {
		required = false
		zap.L().Info(
			"Ignore annotation status, Skip...",
			zap.String("name", metadata.Name),
			zap.String("namespace", metadata.Namespace),
			zap.String("annotation_status_key", annotationStatusKey),
			zap.String("annotation_status_value", status),
		)
	}
	zap.L().Info(fmt.Sprintf("Mutation admission required for %v/%v: %v", metadata.Namespace, metadata.Name, required))
	return required
}

// mutate mutating admission 入口
func (whsvr *WhServer) mutate(ar *admissionv1.AdmissionReview) *admissionv1.AdmissionResponse {
	req := ar.Request
	zap.L().Info(
		"Admission validate start",
		zap.Any("kind", req.Kind),
		zap.Any("namespace", req.Namespace),
		zap.Any("name", req.Name),
		zap.Any("operation", req.Operation),
		zap.Any("user_info", req.UserInfo),
	)

	// 取出资源元数据
	objectMeta, containers, err := extractResourceMetadata(req)
	if err != nil {
		return buildErrorResponse(500, err.Error())
	}
	if objectMeta == nil {
		zap.L().Info("Unsupported resource type, skipped mutation", zap.String("kind", req.Kind.Kind))
		return &admissionv1.AdmissionResponse{Allowed: true}
	}
	return whsvr.handleMutation(objectMeta, containers)
}

// extractResourceMetadata 提取资源元数据
func extractResourceMetadata(req *admissionv1.AdmissionRequest) (*metav1.ObjectMeta, []corev1.Container, error) {

	switch req.Kind.Kind {
	case "Deployment":
		var deployment appsv1.Deployment
		if err := parseResource(req.Object.Raw, &deployment); err != nil {
			return nil, nil, err
		}
		return &deployment.ObjectMeta, deployment.Spec.Template.Spec.Containers, nil

	case "StatefulSet":
		var statefulSet appsv1.StatefulSet
		if err := parseResource(req.Object.Raw, &statefulSet); err != nil {
			return nil, nil, err
		}
		return &statefulSet.ObjectMeta, statefulSet.Spec.Template.Spec.Containers, nil

	case "DaemonSet":
		var daemonSet appsv1.DaemonSet
		if err := parseResource(req.Object.Raw, &daemonSet); err != nil {
			return nil, nil, err
		}
		return &daemonSet.ObjectMeta, daemonSet.Spec.Template.Spec.Containers, nil

	default:
		return nil, nil, nil
	}
}

// handleMutation 添加sidecar, 添加注解, 构造AdmissionResponse
func (whsvr *WhServer) handleMutation(objectMeta *metav1.ObjectMeta, containers []corev1.Container) *admissionv1.AdmissionResponse {
	if !mutationRequired(objectMeta) {
		zap.L().Info(
			"Skipping mutation due to policy check",
			zap.String("namespace", objectMeta.Namespace),
			zap.String("name", objectMeta.Name),
		)
		return &admissionv1.AdmissionResponse{Allowed: true}
	}

	annotations := objectMeta.GetAnnotations()
	if annotations == nil {
		annotations = map[string]string{}
	}

	// 获取添加的sidecar信息
	sidecarName := annotations[annotationSidecarNameKey]
	sidecarImage := annotations[annotationSidecarImageKey]
	if sidecarName == "" || sidecarImage == "" {
		zap.L().Info("Sidecar name or image is empty, skip mutating...")
		return &admissionv1.AdmissionResponse{Allowed: true}
	}

	for _, container := range containers {
		if container.Name == sidecarName {
			zap.L().Info("Sidecar already exists, skip mutating...")
			return &admissionv1.AdmissionResponse{Allowed: true}
		}
	}

	// 创建补丁
	patchBytes, err := whsvr.createPatch(annotations)
	if err != nil {
		return buildErrorResponse(500, err.Error())
	}

	// 构建AdmissionResponse
	return &admissionv1.AdmissionResponse{
		Allowed: true,
		Patch:   patchBytes,
		// 创建补丁类型为JSONPatch
		PatchType: func() *admissionv1.PatchType {
			pt := admissionv1.PatchTypeJSONPatch
			return &pt
		}(),
	}
}

// createPatch 创建资源时, 更新注解的补丁
func (whsvr *WhServer) createPatch(targetAnnotations map[string]string) ([]byte, error) {
	zap.L().Debug(funcParams, zap.Any("targetAnnotations", targetAnnotations))
	var patch []patchOperation
	annotations := map[string]string{annotationStatusKey: "mutated"}
	patch = append(patch, updateAnnotation(targetAnnotations, annotations)...)
	sidecarName := targetAnnotations[annotationSidecarNameKey]
	sidecarImage := targetAnnotations[annotationSidecarImageKey]
	command := targetAnnotations[annotationSidecarCommandKey]
	sidecarCommand := regexp.MustCompile(`\s+`).Split(command, -1)
	zap.L().Debug(
		funcParams,
		zap.Any("sidecarName", sidecarName),
		zap.Any("sidecarImage", sidecarImage),
		zap.Any("sidecarCommand", sidecarCommand),
		zap.Any("command", command),
		zap.Any("targetAnnotations", targetAnnotations),
	)
	patch = append(patch, addSidecar(sidecarName, sidecarImage, sidecarCommand)...)
	zap.L().Debug(funcResult, zap.Any("patch", patch))
	return json.Marshal(patch)
}

// updateAnnotation 更新注解
func updateAnnotation(target map[string]string, added map[string]string) (patch []patchOperation) {
	zap.L().Debug(funcParams, zap.Any("target", target), zap.Any("added", added))
	for key, value := range added {
		if target[key] == "" {
			patch = append(patch, patchOperation{
				Op:    "add", // 添加
				Path:  "/metadata/annotations/" + escapeJson(key),
				Value: value,
			})
		} else {
			patch = append(patch, patchOperation{
				Op:    "replace", // 修改
				Path:  "/metadata/annotations/" + escapeJson(key),
				Value: value,
			})
		}
	}
	zap.L().Debug(funcResult, zap.Any("patch", patch))
	return patch
}

// escapeJson JSON转义, 否则注解/xxx/xxx/xxx的斜线无法正确解析
func escapeJson(s string) string {
	s = strings.ReplaceAll(s, "~", "~0")
	s = strings.ReplaceAll(s, "/", "~1")
	return s
}

// addSidecar 添加sidecar
func addSidecar(sidecarName string, sidecarImage string, sidecarCommand []string) (patch []patchOperation) {
	zap.L().Debug(
		funcParams,
		zap.Any("sidecarName", sidecarName),
		zap.Any("sidecarImage", sidecarImage),
		zap.Any("sidecarCommand", sidecarCommand),
	)
	sidecar := corev1.Container{
		Name:    sidecarName,
		Image:   sidecarImage,
		Command: sidecarCommand,
	}
	patch = append(patch, patchOperation{
		Op:    "add",
		Path:  "/spec/template/spec/containers/0", // sidecar容器添加的位置
		Value: sidecar,
	})
	zap.L().Debug(funcResult, zap.Any("patch", patch))
	return patch
}
